﻿using System;
using System.Collections.Generic;
using System.Text;
using DO.Clustering;

namespace BLL.Relations.VectorDistances
{
    /// <summary>
    /// the pearson correlation coefficient is a measure of how 
    /// highly correlated two variables are. it is a value between 
    /// -1 and 1, where 1 indicates that the variables are perfectly 
    /// correlated, 0 indicates no correlation, and -1 means they are 
    /// perfectly inversely correlated
    /// </summary>
    public class PearsonCorrelationCalculator:IDistanceCalculator
    {
        #region IDistanceCalculator Members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="FeatureFreq1"></param>
        /// <param name="FeatureFreq2"></param>
        /// <param name="contributingFeatures"></param>
        /// <returns></returns>
        public double CalculateDistance(
            Dictionary<int, FeatureFreq> FeatureFreq1,
            Dictionary<int, FeatureFreq> FeatureFreq2,
            ref Dictionary<int, int> contributingFeatures)
        {
            if (FeatureFreq1 == null || FeatureFreq2 == null ||
                FeatureFreq1.Count == 0 || FeatureFreq2.Count == 0)
                return double.PositiveInfinity;

            List<int> unionIDs = new List<int>();
            foreach(int FeatureID1 in FeatureFreq1.Keys)
            {
                unionIDs.Add(FeatureID1);
            }
            foreach(int FeatureID2 in FeatureFreq2.Keys)
            {
                if (!unionIDs.Contains(FeatureID2))
                    unionIDs.Add(FeatureID2);
            }

            int sum1 = 0;
            int sumSq1 = 0;
            foreach(int FeatureID in unionIDs)
            {
                if (FeatureFreq1.ContainsKey(FeatureID))
                {
                    sum1 += FeatureFreq1[FeatureID].Count;
                    sumSq1 +=(int) Math.Pow(FeatureFreq1[FeatureID].Count, 2);
                }
            }
            int sum2 = 0;
            int sumSq2 = 0;
            foreach(int FeatureID in unionIDs)
            {
                if (FeatureFreq2.ContainsKey(FeatureID))
                {
                    sum2 += FeatureFreq2[FeatureID].Count;
                    sumSq2 += (int) Math.Pow(FeatureFreq2[FeatureID].Count, 2);
                }
            }
            int sumProduct = 0;
            foreach(int FeatureID in unionIDs)
            {
                if(FeatureFreq1.ContainsKey(FeatureID) && FeatureFreq2.ContainsKey(FeatureID))
                {
                    sumProduct += FeatureFreq1[FeatureID].Count*FeatureFreq2[FeatureID].Count;
                    contributingFeatures.Add(FeatureID, Math.Min(FeatureFreq1[FeatureID].Count, FeatureFreq2[FeatureID].Count));
                }
            }
            double density =
                Math.Sqrt((sumSq1 - Math.Pow(sum1, 2)/FeatureFreq1.Count)*(sumSq2 - Math.Pow(sum2, 2)/FeatureFreq2.Count));
            double number = sumProduct - (sum1*sum2)/unionIDs.Count;
            double pearsonDistance = number/density;
            double docLenScale = (double)contributingFeatures.Count * 2 /
                                 (FeatureFreq1.Count + FeatureFreq2.Count - contributingFeatures.Count);
            pearsonDistance = pearsonDistance*docLenScale;
            double contributingFeatureScale =
                FeatureWeightCalculator.TaminoCommonFeatureScale(
                    FeatureFreq1.Count, FeatureFreq2.Count,
                    contributingFeatures.Count);
            return pearsonDistance*contributingFeatureScale;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="FeatureFreq1"></param>
        /// <param name="FeatureFreq2"></param>
        /// <param name="FeatureWeights"></param>
        /// <param name="useNGramSimilarity"></param>
        /// <param name="contributingFeatures"></param>
        /// <returns></returns>
        public double CalculateDistance(
            Dictionary<int, FeatureFreq> FeatureFreq1,
            Dictionary<int, FeatureFreq> FeatureFreq2,
            Dictionary<int, double> FeatureWeights,
            bool useNGramSimilarity, int ngramLen,
            ref Dictionary<int, int> contributingFeatures)
        {
            if (FeatureFreq1 == null || FeatureFreq2 == null ||
               FeatureFreq1.Count == 0 || FeatureFreq2.Count == 0)
                return double.PositiveInfinity;

            List<int> unionIDs = new List<int>();
            foreach (int FeatureID1 in FeatureFreq1.Keys)
            {
                unionIDs.Add(FeatureID1);
            }
            foreach (int FeatureID2 in FeatureFreq2.Keys)
            {
                if (!unionIDs.Contains(FeatureID2))
                    unionIDs.Add(FeatureID2);
            }

            double sum1 = 0;
            double sumSq1 = 0;
            Dictionary<int, Dictionary<int, double>> featureSimilarityMatrix =
               FeatureSimilarityMatrix.BuildFeatureDistanceMatrixUsingNgram(
                   FeatureFreq1, FeatureFreq2);

            foreach (int FeatureID in unionIDs)
            {
                double t1 = 0;
                if (FeatureFreq1.ContainsKey(FeatureID))
                {
                    t1 = FeatureFreq1[FeatureID].Count*FeatureFreq1[FeatureID].Weight;
                }
                else if(useNGramSimilarity && ngramLen>0)
                {
                    Dictionary<int, double> similarFeatures = featureSimilarityMatrix[FeatureID];
                    foreach (int similarFeatureID in similarFeatures.Keys)
                    {
                        if (FeatureFreq1.ContainsKey(similarFeatureID) &&
                            FeatureFreq1[similarFeatureID].Count*FeatureFreq1[similarFeatureID].Weight > t1)
                            t1 = FeatureFreq1[similarFeatureID].Count * FeatureFreq1[similarFeatureID].Weight;
                    }
                }
                sum1 += t1;
                double sq1 = Math.Pow(t1, 2);
                sumSq1 += sq1;
            }
            double sum2 = 0;
            double sumSq2 = 0;
            foreach (int FeatureID in unionIDs)
            {
                double t2 = 0;
                if (FeatureFreq2.ContainsKey(FeatureID))
                {
                    t2 = FeatureFreq2[FeatureID].Count*FeatureFreq2[FeatureID].Weight;
                }
                else if(useNGramSimilarity && ngramLen>0)
                {
                    Dictionary<int, double> similarFeatures = featureSimilarityMatrix[FeatureID];
                    foreach (int similarFeatureID in similarFeatures.Keys)
                    {
                        if (FeatureFreq2.ContainsKey(similarFeatureID) &&
                            FeatureFreq2[similarFeatureID].Count * FeatureFreq2[similarFeatureID].Weight > t2)
                            t2 = FeatureFreq2[similarFeatureID].Count * FeatureFreq2[similarFeatureID].Weight;
                    }
                }
                sum2 += t2;
                sumSq2 += Math.Pow(t2, 2);
            }
            double sumProduct = 0;
            foreach (int FeatureID in unionIDs)
            {
                double sp = 0;
                if (FeatureFreq1.ContainsKey(FeatureID) && FeatureFreq2.ContainsKey(FeatureID))
                {
                    sp = FeatureFreq1[FeatureID].Count * FeatureFreq1[FeatureID].Count;
                    contributingFeatures.Add(FeatureID, Math.Min(FeatureFreq1[FeatureID].Count, FeatureFreq2[FeatureID].Count));
                }
                else
                {
                    if (useNGramSimilarity && ngramLen>0)
                    {
                        Dictionary<int, double> similarFeatures = featureSimilarityMatrix[FeatureID];
                        foreach (int similarFeatureID in similarFeatures.Keys)
                        {
                            if (FeatureFreq1.ContainsKey(FeatureID) && FeatureFreq2.ContainsKey(similarFeatureID))
                            {
                                if (FeatureFreq1[FeatureID].Count * FeatureFreq1[FeatureID].Count > sp)
                                    sp = FeatureFreq1[FeatureID].Count * FeatureFreq1[FeatureID].Count;
                            }
                            else if (FeatureFreq2.ContainsKey(FeatureID) && FeatureFreq1.ContainsKey(similarFeatureID))
                            {
                                if (FeatureFreq2[FeatureID].Count * FeatureFreq2[FeatureID].Count > sp)
                                    sp = FeatureFreq2[FeatureID].Count * FeatureFreq2[FeatureID].Count;
                            }
                        }
                    }
                }
                sumProduct += sp;
            }
            double density =
                Math.Sqrt((sumSq1 - Math.Pow(sum1, 2) / FeatureFreq1.Count) * (sumSq2 - Math.Pow(sum2, 2) / FeatureFreq2.Count));
            double number = sumProduct - (sum1 * sum2) / unionIDs.Count;
            double pearsonDistance = number / density;
            double docLenScale = (double)contributingFeatures.Count * 2 /
                                 (FeatureFreq1.Count + FeatureFreq2.Count - contributingFeatures.Count);
            pearsonDistance = pearsonDistance*docLenScale;

            //Dictionary<int, double> contributingFeatureWeights = new Dictionary<int, double>();
            //foreach (int sharedFeatureID in contributingFeatures.Keys)
            //{
            //    if (FeatureWeights.ContainsKey(sharedFeatureID))
            //    {
            //        contributingFeatureWeights.Add(sharedFeatureID, FeatureWeights[sharedFeatureID]);
            //    }
            //    else
            //    {
            //        contributingFeatureWeights.Add(sharedFeatureID, 0);
            //    }
            //}
            double contributingFeatureScale =
                FeatureWeightCalculator.TaminoWeightedFeatureScale(
                    FeatureFreq1, FeatureFreq2,
                    FeatureWeights);

            return pearsonDistance*contributingFeatureScale;
        }

        public double CalculateDistance(Dictionary<int, FeatureFreq> FeatureFreq1, Dictionary<int, FeatureFreq> FeatureFreq2,
                                        Dictionary<int, double> FeatureWeights, Dictionary<int, string> FeatureStemmings1,
                                        Dictionary<string, List<int>> FeatureStemmings2,
                                        ref Dictionary<int, int> contributingFeatures)
        {
            if (FeatureFreq1 == null || FeatureFreq2 == null ||
               FeatureFreq1.Count == 0 || FeatureFreq2.Count == 0)
                return double.PositiveInfinity;

            List<int> unionIDs = new List<int>();
            foreach (int FeatureID1 in FeatureFreq1.Keys)
            {
                unionIDs.Add(FeatureID1);
            }
            foreach (int FeatureID2 in FeatureFreq2.Keys)
            {
                if (!unionIDs.Contains(FeatureID2))
                    unionIDs.Add(FeatureID2);
            }

            double sum1 = 0;
            double sumSq1 = 0;
            

            foreach (int FeatureID in unionIDs)
            {
                double t1 = 0;
                if (FeatureFreq1.ContainsKey(FeatureID))
                {
                    t1 = FeatureFreq1[FeatureID].Count*FeatureFreq1[FeatureID].Weight;
                }
                else 
                {
                    List<int> sharedFeatureIDsAll = FeatureStemmings2[FeatureStemmings1[FeatureID]];
                    foreach (int similarFeatureID in sharedFeatureIDsAll)
                    {
                        if (FeatureFreq1.ContainsKey(similarFeatureID) &&
                            FeatureFreq1[similarFeatureID].Count*FeatureFreq1[similarFeatureID].Weight > t1)
                            t1 = FeatureFreq1[similarFeatureID].Count * FeatureFreq1[similarFeatureID].Weight;
                    }
                }
                sum1 += t1;
                double sq1 = Math.Pow(t1, 2);
                sumSq1 += sq1;
            }
            double sum2 = 0;
            double sumSq2 = 0;
            foreach (int FeatureID in unionIDs)
            {
                double t2 = 0;
                if (FeatureFreq2.ContainsKey(FeatureID))
                {
                    t2 = FeatureFreq2[FeatureID].Count*FeatureFreq2[FeatureID].Weight;
                }
                else 
                {
                    List<int> sharedFeatureIDsAll = FeatureStemmings2[FeatureStemmings1[FeatureID]];
                    foreach (int similarFeatureID in sharedFeatureIDsAll)
                    {
                        if (FeatureFreq2.ContainsKey(similarFeatureID) &&
                            FeatureFreq2[similarFeatureID].Count * FeatureFreq2[similarFeatureID].Weight > t2)
                            t2 = FeatureFreq2[similarFeatureID].Count * FeatureFreq2[similarFeatureID].Weight;
                    }
                }
                sum2 += t2;
                sumSq2 += Math.Pow(t2, 2);
            }
            double sumProduct = 0;
            foreach (int FeatureID in unionIDs)
            {
                double sp = 0;
                if (FeatureFreq1.ContainsKey(FeatureID) && FeatureFreq2.ContainsKey(FeatureID))
                {
                    sp = FeatureFreq1[FeatureID].Count * FeatureFreq1[FeatureID].Count;
                    contributingFeatures.Add(FeatureID, Math.Min(FeatureFreq1[FeatureID].Count, FeatureFreq2[FeatureID].Count));
                }
                else
                {
                    List<int> sharedFeatureIDsAll = FeatureStemmings2[FeatureStemmings1[FeatureID]];
                    foreach (int similarFeatureID in sharedFeatureIDsAll)
                    {
                        if (FeatureFreq1.ContainsKey(FeatureID) && FeatureFreq2.ContainsKey(similarFeatureID))
                        {
                            if (FeatureFreq1[FeatureID].Count*FeatureFreq1[FeatureID].Count > sp)
                                sp = FeatureFreq1[FeatureID].Count*FeatureFreq1[FeatureID].Count;
                        }
                        else if (FeatureFreq2.ContainsKey(FeatureID) && FeatureFreq1.ContainsKey(similarFeatureID))
                        {
                            if (FeatureFreq2[FeatureID].Count*FeatureFreq2[FeatureID].Count > sp)
                                sp = FeatureFreq2[FeatureID].Count*FeatureFreq2[FeatureID].Count;
                        }
                    }
                }
                sumProduct += sp;
            }
            double density =
                Math.Sqrt((sumSq1 - Math.Pow(sum1, 2) / FeatureFreq1.Count) * (sumSq2 - Math.Pow(sum2, 2) / FeatureFreq2.Count));
            double number = sumProduct - (sum1 * sum2) / unionIDs.Count;
            double pearsonDistance = number / density;
            double docLenScale = (double)contributingFeatures.Count * 2 /
                                 (FeatureFreq1.Count + FeatureFreq2.Count - contributingFeatures.Count);
            pearsonDistance = pearsonDistance*docLenScale;

            double contributingFeatureScale =
                FeatureWeightCalculator.TaminoWeightedFeatureScale(
                    FeatureFreq1, FeatureFreq2,
                    FeatureWeights);

            return pearsonDistance*contributingFeatureScale;
        }

        #endregion
    }
}
